package com.lecture.junit.tdd.example;


import com.lecture.junit.tdd.example.exception.*;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Test;

import javax.print.attribute.standard.DateTimeAtCompleted;

import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.*;

public class LogonServiceTest {

    public static final String USER = "user";
    public static final String ANOTHER_USER = "another_user";
    public static final String PASSWORD = "password";
    public static final String NEW_PASSWORD = "new_password";

    IAccount accountUser;
    IAccountRepository accountRepository;
    LoginService service;

    @Before
    public void init() {
        accountUser = mock(IAccount.class);
        when(accountUser.getUserName()).thenReturn(USER);
        when(accountUser.getLastTimeChangePassword()).thenReturn(DateTime.now());
        when(accountUser.getLastTimeLoggedIn()).thenReturn(DateTime.now());
        when(accountUser.getLastTimeLoggedOut()).thenReturn(DateTime.now());

        accountRepository = mock(IAccountRepository.class);
        when(accountRepository.find(anyString())).thenReturn(accountUser);

        service = new LoginService(accountRepository);
    }

    public void willPasswordMatch(boolean flag) {
        when(accountUser.passwordMatches(anyString())).thenReturn(flag);
    }

    @Test
    public void testLogin_shouldLogin_whenUserNameAndPasswordPassed() throws AccountException {
        willPasswordMatch(true);
        service.login(USER, PASSWORD);
        verify(accountUser, times(1)).setLoggedIn();
    }

    @Test
    public void testLogin_shouldRevokeAccount_whenThreeLoginAttemptsFail() throws AccountException {
        willPasswordMatch(false);

        service.login(USER, PASSWORD);
        service.login(USER, PASSWORD);
        service.login(USER, PASSWORD);

        verify(accountUser).setRevoked();
    }

    @Test
    public void testLogin_shouldNotSetLoggedIn_whenPasswordNotMatches() throws AccountException {
        willPasswordMatch(false);

        service.login(USER, PASSWORD);

        verify(accountUser, never()).setLoggedIn();
    }

    @Test
    public void testLogin_shouldNotRevoke_whenOneAccFailsTwoTimesAndOtherFailsOneTime() throws AccountException {
        willPasswordMatch(false);

        IAccount anotherAccount = mock(IAccount.class);
        when(anotherAccount.getUserName()).thenReturn(ANOTHER_USER);
        doReturn(false).when(anotherAccount).passwordMatches(anyString());
        when(accountRepository.find(ANOTHER_USER)).thenReturn(anotherAccount);   //??

        service.login(USER, PASSWORD);
        service.login(USER, PASSWORD);

        service.login(ANOTHER_USER, PASSWORD);

        verify(anotherAccount, never()).setRevoked();
    }

    @Test(expected = AccountAlreadyLoggedInException.class)
    public void testLogin_shouldNotifyWithException_whenLoggedIndAccTriesToLogin() throws AccountException {
        willPasswordMatch(true);
        doReturn(true).when(accountUser).isLoggedIn();

        service.login(USER, PASSWORD);
    }

    @Test(expected = AccountNotFoundException.class)
    public void testLogin_shouldThrowException_whenAccIsNotFound() throws AccountException {
        doReturn(null).when(accountRepository).find(anyString());

        service.login(USER, PASSWORD);
    }

    @Test(expected = AccountIsRevokedException.class)
    public void testLogin_shouldNotLoginAndThrowException_whenAccRevoked() throws AccountException {
        willPasswordMatch(true);
        doReturn(true).when(accountUser).isRevoked();

        service.login(USER, PASSWORD);
    }

    @Test
    public void testLogin_shouldRestBackToInitialState_afterSuccessfulLogin() throws AccountException {
        willPasswordMatch(false);
        service.login(USER, PASSWORD);
        service.login(USER, PASSWORD);
        willPasswordMatch(true);
        service.login(USER, PASSWORD);
        willPasswordMatch(false);
        service.login(USER, PASSWORD);

        verify(accountUser, never()).setRevoked();
    }



    @Test(expected = PasswordExpiredException.class)
    public void testLogin_shouldThrowException_whenPasswordTimeExpired() throws AccountException {
        doReturn(true).when(accountUser).isPasswordExpired();
        willPasswordMatch(true);

        service.login(USER, PASSWORD);
    }

    @Test
    public void testLogin_shouldChangePassword_whenPasswordExpiredAndNewPasswordIsValid() throws AccountException {
        doReturn(true).when(accountUser).isPasswordExpired();
        doReturn(false).when(accountUser).isOldPassword(NEW_PASSWORD);
        willPasswordMatch(true);

        service.changePassword(USER, PASSWORD, NEW_PASSWORD);

        verify(accountUser, times(1)).changePassword(PASSWORD, NEW_PASSWORD);
    }

    @Test(expected = InvalidPasswordException.class)
    public void testLogin_shouldThrowException_whenPasswordLifeTimeExpiredAndNewPasswordIsInvalid() throws AccountException {
        doReturn(true).when(accountUser).isPasswordExpired();
        doReturn(true).when(accountUser).isOldPassword(NEW_PASSWORD);
        doReturn(false).when(accountUser).isRevoked();
        willPasswordMatch(true);

        service.changePassword(USER, PASSWORD, NEW_PASSWORD);
    }

    @Test(expected = AccountIsRevokedException.class)
    public void testLogin_shouldThrowException_whenAccountLifeTimeExpired() throws AccountException {
        doReturn(DateTime.now().minusDays(50)).when(accountUser).getLastTimeChangePassword();
        doReturn(false).when(accountUser).isPasswordExpired();
        willPasswordMatch(true);

        service.login(USER, PASSWORD);

        verify(accountUser, times(1)).setRevoked();
    }

    @Test(expected = PasswordExpiredException.class)
    public void testLogin_shouldThrowException_whenPasswordLifeTimeExpired() throws AccountException {
        doReturn(DateTime.now().minusDays(35)).when(accountUser).getLastTimeChangePassword();
        doReturn(false).when(accountUser).isPasswordExpired();
        doReturn(false).when(accountUser).isLoggedIn();
        doReturn(false).when(accountUser).isRevoked();
        willPasswordMatch(true);

        service.login(USER, PASSWORD);
    }

}
